Skip to main content

Events

BindAI uses events to expose important runtime activity without requiring application code to be tightly coupled to the component producing that activity. The event system is built around four core pieces:
  • Event — the base event object.
  • EventBus — a synchronous publisher/subscriber mechanism.
  • ExecutionContext — the execution-scoped context that owns an event bus and execution ID.
  • EventRecorder and EventTrigger — runtime utilities for recording and reacting to events.
Events are useful for logging, diagnostics, monitoring, automation, and application integrations.

Event Architecture

The basic flow is:
A component creates an event and publishes it through an EventBus. Subscribers receive the event synchronously. The event system does not require subscribers to know which component produced the event. They only need to subscribe to the relevant event name.

Event Objects

All BindAI events derive from the Event base class. The base event contains three pieces of metadata:
An event instance contains:

Payload

payload is a dictionary containing event-specific information. It defaults to an empty dictionary. Event-specific event classes may expose additional attributes alongside the payload.

Event ID

Every event receives a unique ID when it is created.
The ID is generated using uuid4().

Timestamp

Every event receives a UTC timestamp:
The timestamp is created when the event instance is constructed.

Event name

Every event exposes a string name:
Event names are used by EventBus subscriptions.

EventBus

EventBus is the central event publisher and subscriber mechanism.
The bus supports:
  • subscribing to an event name
  • unsubscribing from an event name
  • publishing events
  • wildcard subscriptions

Subscribing to Events

Subscribe a callable to an event name:
The handler receives the complete event object. A handler therefore has access to:

Publishing Events

Publish an event through the bus:
The bus finds handlers registered for the event’s name and invokes them. Handlers are invoked synchronously. For example:
The handler executes as part of the publish() call.

Wildcard Subscriptions

The event bus supports the special event name:
A wildcard subscriber receives every event published through the bus.
This is particularly useful for:
  • recording events
  • logging
  • diagnostics
  • general-purpose observability integrations
For example:
The wildcard handler receives events regardless of their specific event name.

Unsubscribing

Remove a previously registered handler with:
The handler must be the same callable that was subscribed. Wildcard subscriptions are removed using:

Handler Isolation

Event handlers are isolated from the publisher. The event bus invokes each handler inside a try/except block. If a handler raises an exception:
  1. the exception is logged;
  2. the event bus continues processing the remaining handlers.
For example:
A failure in failing_handler does not prevent working_handler from being invoked. This behavior is important for diagnostic and observability handlers because a faulty handler should not normally stop event delivery to other subscribers.

Framework Event Types

BindAI defines a collection of standard event names. The current event types include: The event type definitions provide stable names that can be used by subscribers and integrations.

Agent Events

The core agent event types are:
The corresponding event classes are:
For example:
These events represent the beginning and end of agent execution.

Model Events

Model interaction exposes two event types:
The event classes are:
A model request event contains the associated model request:
A model response event contains the associated model response:
These events can be used to observe model activity without changing the provider implementation. For example:

Tool Events

Tool execution exposes:
The corresponding event class is:
The event identifies the tool that was executed:
For example:
This is useful for application-level logging and diagnostics around tool execution.

ExecutionContext

An ExecutionContext represents the context associated with one execution.
The context contains an execution ID:
The ID is generated automatically when the context is created. The context also owns an event bus:
Therefore execution-scoped code can publish or subscribe through:
The context additionally contains:
  • execution state
  • dependency container
  • registry
  • variables
  • metadata
  • execution data
A simplified example:

Execution IDs

An ExecutionContext always has an execution_id.
This ID provides a stable identifier for that execution. Some framework events include the execution ID in their event payload. When an event contains:
the event can be associated with the corresponding execution. It is important not to assume that every event automatically contains an execution_id. Event payloads are event-specific, and only events that include the field can be indexed by execution ID by EventRecorder.

EventRecorder

BindAI provides EventRecorder in the runtime package for lightweight in-memory event recording.
Create a recorder with an existing event bus:
The recorder automatically subscribes to the bus using the wildcard event:
It therefore records every event published through that bus.

Recording Events

Once attached, events are recorded automatically:
The event can then be retrieved:
The recorder stores events in emission order.

Reading All Recorded Events

Use:
The method returns a copy of the recorded event list. For example:
The returned list can be inspected without modifying the recorder’s internal storage.

Reading Events for an Execution

If events contain an execution_id in their payload, the recorder indexes them by execution. Use:
For example:
Only events whose payload contains a string execution_id are included in this execution-specific index.

Clearing Recorded Events

Remove all recorded events with:
This clears both:
  • the complete event list
  • the execution-specific event index
After clearing:
returns an empty list.

Closing an EventRecorder

When the recorder is no longer needed:
close() unsubscribes the recorder’s handler from the event bus. The recorder therefore stops receiving newly published events. Closing a recorder does not require destroying the event bus itself.

Recorder Lifecycle

A typical recorder lifecycle is:
If the same recorder needs to be reused after closing, it must be attached again through the supported event-bus subscription mechanism rather than assuming that close() keeps it subscribed.

Thread Safety

EventRecorder protects its internal event collections with a lock. Recording, reading, and clearing events therefore use synchronized access to the recorder’s internal state. The recorder remains intentionally lightweight and stores events in memory. It is not a persistent event database.

EventRecorder Example

A simple execution recording pattern is:
The exact set of execution-indexed events depends on which published events include the execution ID in their payload.

EventTrigger

BindAI also provides an event-driven automation primitive:
An EventTrigger connects an event name to a callable target. Create one with:
The target receives the matching event:

Attaching a Trigger

A trigger is not subscribed to the event bus until it is attached:
After attachment, matching events invoke the target. For example:
The trigger subscribes to the configured event name. Calling attach() more than once does not create duplicate subscriptions.

Detaching a Trigger

Detach a trigger with:
After detaching, the trigger no longer receives events from that subscription. Calling detach() when the trigger is already detached is safe.

Enabling and Disabling a Trigger

Triggers are enabled by default. Disable a trigger with:
Re-enable it with:
These methods return the trigger, so they can be chained:

Disabled Triggers

Disabling a trigger does not unsubscribe it from the event bus. Instead, the trigger remains attached but ignores incoming events while disabled. This makes it possible to temporarily pause a trigger:
Use detach() when the trigger should no longer be connected to the event bus.

EventTrigger Example

A complete example:
The trigger now reacts to model.responded events published through bus. To temporarily disable it:
To resume:
To permanently detach it from the bus:

EventBus vs EventTrigger

EventBus and EventTrigger solve different parts of the event system.

EventBus

Use EventBus when application code needs to:
  • publish events
  • subscribe handlers
  • unsubscribe handlers
  • observe multiple event types
  • register wildcard handlers
Example:

EventTrigger

Use EventTrigger when an event should activate an automation target and the trigger needs an explicit lifecycle:
The trigger is built on top of EventBus.

Event Handling Patterns

Logging

A wildcard subscriber can provide simple event logging:

Targeted monitoring

Subscribe only to the event of interest:

Execution recording

Attach an EventRecorder:

Automation

Attach an EventTrigger:
These patterns can be combined.

Keeping Event Handlers Lightweight

Event handlers execute synchronously during event publication. Handlers should therefore avoid unnecessary blocking work. For example, an event handler that performs a long-running operation directly inside publish() can increase the time spent publishing the event. A lightweight handler can instead collect the information needed for later processing. For example:
The handler should generally perform only the work required for its event-processing responsibility.

Event Handler Errors

Because EventBus isolates handler exceptions, a failing handler does not normally prevent other subscribed handlers from running. The event bus logs the handler exception. Application code should still treat handler failures as real operational errors and monitor the corresponding logs.

Event Names

Use the framework’s existing event names when subscribing to framework events. For example:
When defining application-specific events, use clear names that identify the event being emitted. For example:
Avoid ambiguous names that make event consumers difficult to understand.

Events and Execution

Events are useful because they separate the component performing an operation from the component observing it. For example, a model execution path can publish a model event while an independent subscriber records or monitors that event. The publishing component does not need to know which subscribers exist. This separation can be represented as:
The same event can therefore support several independent consumers.

Events and Automation

EventTrigger provides the bridge between events and automation. The basic pattern is:
For example:
The target is invoked only when the configured event is published and the trigger is enabled.

Testing Event Handlers

Because EventBus is synchronous, event handling can be tested directly. For example:
This allows event-driven behavior to be tested without requiring asynchronous infrastructure.

Testing Event Recording

EventRecorder can be used to inspect events emitted during a test:
Execution-specific recording can be tested when an event contains an execution ID:

Testing Event Triggers

Triggers can also be tested synchronously:
Disabled behavior can be tested separately:
After re-enabling:

Events vs Hooks

Events and hooks provide different integration mechanisms.

Events

Events are published through an EventBus and can have multiple independent subscribers. Use events when multiple components may need to observe the same activity.

Agent lifecycle hooks

Agent hooks are attached directly to an agent and participate in the agent lifecycle. They are appropriate when behavior belongs specifically to the lifecycle of that agent. Events are broader and decouple the producer from its consumers.

Events and Callbacks

Agent callbacks are a separate mechanism from the core EventBus. Use the event system when the behavior should consume a published framework event. Use an agent callback when the behavior is specifically associated with the agent’s callback lifecycle. The two mechanisms should not be treated as interchangeable APIs.

Event Data and Payloads

Do not assume that all events have the same payload structure. The base Event guarantees:
Specific event classes may expose additional information. For example, model events expose their associated request or response, while tool events expose the tool name. Consumers should therefore use the documented event-specific attributes for the event type they subscribe to.

Observability Architecture

The event system provides the foundation for runtime observation without requiring every consumer to be embedded inside execution code. A typical architecture is:
This architecture allows the same event stream to support multiple independent concerns.

Best Practices

Subscribe only to events you need

Prefer targeted subscriptions when possible:
Use wildcard subscriptions when the application genuinely needs every event.

Keep handlers lightweight

Event delivery is synchronous. Avoid unnecessary blocking work inside handlers.

Use EventRecorder for local inspection

EventRecorder is useful for debugging and tests when events need to be inspected after execution.

Close recorders when finished

Detach recorders that are no longer needed:

Detach unused triggers

Remove triggers that should no longer listen:

Disable triggers temporarily

Use:
when the trigger should remain attached but temporarily inactive.

Do not assume execution IDs

Only use execution-specific recording for events that actually include execution_id in their payload.

Keep event consumers decoupled

The purpose of the event system is to allow consumers to observe activity without embedding that consumer logic into the component producing the event.

Summary

BindAI’s event system consists of a small set of composable primitives:
  • Event provides event metadata and payloads.
  • EventBus publishes events and manages subscriptions.
  • wildcard subscriptions allow observation of all events on a bus.
  • handler exceptions are isolated and logged by the event bus.
  • ExecutionContext provides an execution ID and an execution-scoped EventBus.
  • framework events cover application, agent, workflow, node, model, tool, memory, MCP, and human-task event names.
  • EventRecorder stores events in memory and can index events that contain an execution ID.
  • EventTrigger connects a specific event to an automation target.
  • triggers can be attached, detached, enabled, and disabled.
  • event delivery is synchronous.
Together these APIs provide the foundation for event-driven observation and automation while keeping event producers and consumers loosely coupled.